package com.light.applet.oauth.controller;

import com.light.applet.oauth.security.jwt.custom.CustomAccessTokenConverter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 魏彪
 */
@Slf4j
@RestController
@RequestMapping("/login")
public class OAuth2Controller extends AbstractOauth {

    private final OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator();

    @Autowired
    private ResourceServerTokenServices resourceServerTokenServices;

    private final AccessTokenConverter accessTokenConverter = new CustomAccessTokenConverter();

    @Autowired
    private TokenGranter tokenGranter;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private OAuth2RequestFactory defaultOAuth2RequestFactory;

    @PostMapping("/token")
    public ResponseEntity<OAuth2AccessToken> postAccessToken(@RequestBody Map<String, String> parameters) {
        check(parameters);
        ClientDetails authenticatedClient = clientDetailsService.loadClientByClientId(parameters.get("clientId"));
        TokenRequest tokenRequest = defaultOAuth2RequestFactory.createTokenRequest(parameters, authenticatedClient);
        if (authenticatedClient != null) {
            oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
        }
        if (!StringUtils.hasText(tokenRequest.getGrantType())) {
            throw new InvalidRequestException("Missing grant type");
        }
        if (tokenRequest.getGrantType().equals("implicit")) {
            throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
        }

        if (isAuthCodeRequest(parameters)) {
            // The scope was requested or determined during the authorization step
            if (!tokenRequest.getScope().isEmpty()) {
                log.debug("Clearing scope of incoming token request");
                tokenRequest.setScope(Collections.emptySet());
            }
        }

        if (isRefreshTokenRequest(parameters)) {
            // A refresh token has its own default scopes, so we should ignore any added by the factory here.
            tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
        }

        OAuth2AccessToken token = tokenGranter.grant(tokenRequest.getGrantType(), tokenRequest);
        if (token == null) {
            throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
        }
        System.out.println("----");
        return ResponseEntity.ok(token);
    }


    @GetMapping(value = "/check_token/{token}")
    @SuppressWarnings("all")
    public Map<String, ?> checkToken(@PathVariable String token) {
        OAuth2AccessToken auth2AccessToken = resourceServerTokenServices.readAccessToken(token);
        if (auth2AccessToken == null) {
            throw new InvalidTokenException("Token was not recognised");
        }

        if (auth2AccessToken.isExpired()) {
            Map<String, Object> response = new HashMap<>();
            response.put("active", false);
            return response;
            // throw new InvalidTokenException("Token has expired");
        }

        OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(auth2AccessToken.getValue());

        Map<String, Object> response = (Map<String, Object>) accessTokenConverter.convertAccessToken(auth2AccessToken, authentication);
        response.remove("principal");

        // gh-1070
        // Always true if token exists and not expired
        response.put("active", true);

        return response;
    }

    /**
     * 当前登陆用户信息<br>
     * <p>
     * security获取当前登录用户的方法是SecurityContextHolder.getContext().getAuthentication()<br>
     * 返回值是接口org.springframework.security.core.Authentication，又继承了Principal<br>
     * 这里的实现类是org.springframework.security.oauth2.provider.OAuth2Authentication<br>
     * <p>
     * 因此这只是一种写法，下面注释掉的三个方法也都一样，这四个方法任选其一即可，也只能选一个，毕竟uri相同，否则启动报错<br>
     * 2018.05.23改为默认用这个方法，好理解一点
     *
     * @return Authentication
     */
    @GetMapping("/me")
    public Object principal(Authentication authentication) {
        log.info("user-me:{}", authentication);
        log.info("user-me:{}", authentication);
        log.info("user-me:{}", authentication);
        return authentication;
    }

}
